﻿using D.UtilCore.Dtos;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System.Net;

namespace D.UtilCore.Filters
{
    /// <summary>
    /// 接口限速
    /// 使用方法：在函数上定义特性[RateLimit(8)]
    /// </summary>
    public class RateLimitAttribute : Attribute
    {
        private SemaphoreSlim _sem;

        public SemaphoreSlim Sem
        {
            get
            {
                return _sem;
            }
        }

        /// <summary>
        /// 超时时间(单位:毫秒)
        /// </summary>
        private int _timeout;

        /// <summary>
        /// 超时时间(单位:毫秒)
        /// </summary>
        public int Timeout
        {
            get
            {
                return _timeout;
            }
        }

        /// <summary>
        /// 接口限速
        /// </summary>
        /// <param name="limitCount">限制并发数量</param>
        /// <param name="timeout">超时时间(单位:秒)</param>
        public RateLimitAttribute(int limitCount = 1, int timeout = 0)
        {
            _sem = new SemaphoreSlim(limitCount, limitCount);
            _timeout = timeout * 1000;
        }
    }

    /// <summary>
    /// 限制接口并发量的拦截器RateLimitFilter，接口限速需求
    /// 1、写了一个接口，参数可以传多个人员，也可以传单个人员，时间范围限制最长一个月。简单来说，当传单个人员时，接口耗时很短，当传多个人员时，一般人员会较多，接口耗时较长，一般耗时几秒。
    /// 2、当传多个人员时，并发量高时，接口的耗时就很长了，比如100个用户并发请求，耗时可长达几十秒，甚至1分钟。
    /// 3、所以需求是，当传单个人员时，不限制。当传多个人员时，限制并发数量。如果并发用户数少于限制数，那么所有用户都能成功。如果并发用户数，超出限制数，那么超出的用户请求失败，并提示"当前进行XXX查询的用户太多，请稍后再试"。
    /// 4、这样也可以减轻被请求的ES集群的压力。
    /// 使用方法：注册拦截器
    /// builder.Services.AddMvc(options =>
    /// {
    ///     options.Filters.Add<RateLimitFilter>();
    /// });
    /// 假如设置RateLimit(1, 0)，即并发1，超时时间0，那么当100个并发请求时，只有1个成功，99个失败。
    /// 假如接口耗时2秒，设置RateLimit(1, 10)，即并发1，超时时间10秒，那么当100个并发请求时，会有大约5个成功，95个失败。第1个成功的接口请求耗时大约2秒，后续成功的4个，请求耗时依次增加。
    /// 当设置了并发量和超时时间后，接口平均一秒钟能被请求多少次，取决于接口耗时，耗时短的接口平均每秒能被请求的次数多，耗时长的接口平均每秒能被请求的次数少。
    /// 好文要顶 关注我 收藏该文
    /// </summary>
    public class ExtRateLimitFilterAttrbute : ActionFilterAttribute
    {
        public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
        {
            Type controllerType = context.Controller.GetType();
            object arg = context.ActionArguments.Values.ToList()[0];
            var rateLimit = context.ActionDescriptor.EndpointMetadata.OfType<RateLimitAttribute>().FirstOrDefault();

            bool isLimit = false;
            if (rateLimit != null)
            {

                isLimit = true;
                SemaphoreSlim sem = rateLimit.Sem;

                if (await sem.WaitAsync(rateLimit.Timeout))
                {
                    try
                    {
                        await next.Invoke();
                    }
                    catch
                    {
                        throw;
                    }
                    finally
                    {
                        sem.Release();
                    }
                }
                else
                {
                    var routeList = context.RouteData.Values.Values.ToList();
                    routeList.Reverse();
                    var route = string.Join('/', routeList.ConvertAll(a => a.ToString()));
                    var msg = $"当前访问{route}接口的用户数太多，请稍后再试";
                    context.Result = new ObjectResult(new ApiResult<int>
                    {
                        Code = StatusCodeEnum.Waring,
                        Message = "当前查询的用户太多，请稍后再试。"
                    });
                }
            }
            if (!isLimit)
            {
                await next.Invoke();
            }
        }
    }
}
